package kpsmart.model.events;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;

/**
 * @author Oliver Behncke
 *
 */
public class Events {

    private static final String JAXBCONTEXT = "kpsmart.model.events";

    private Events() {
    }

    /**
     * Private helper function that returns the root element of the logfile
     *
     * @return Simulation
     * @throws JAXBException
     */
    private static Simulation getSimulation(String logfilepath) throws JAXBException, UnmarshalException {
        JAXBContext context = JAXBContext.newInstance(JAXBCONTEXT);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        return (Simulation) unmarshaller.unmarshal(new File(logfilepath));
    }

    /**
     * Save an event to the logfile
     *
     * @param event
     * @throws JAXBException
     * @throws FileNotFoundException
     */
    public static void saveEvent(Event event, String logfilepath) throws JAXBException, FileNotFoundException {
        JAXBContext context = JAXBContext.newInstance(JAXBCONTEXT);
        Simulation simulation = getSimulation(logfilepath);
        List<Event> list = simulation.getPriceOrCostOrMail();
        list.add(event);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                new Boolean(true));
        marshaller.marshal(simulation, new FileOutputStream(logfilepath));
    }

    /**
     * Get a list of all events in log file
     *
     * @return
     * @throws JAXBException
     */
    public static List<Event> getEvents(String logfilepath) throws JAXBException, UnmarshalException {
        List<Event> result = new ArrayList<Event>(getSimulation(logfilepath).getPriceOrCostOrMail());
        Collections.sort(result);
        return result;
    }

    /**
     * Get JAXBContext as String
     *
     * @return jaxbcontext
     */
    public static String getJAXBContext() {
        return JAXBCONTEXT;
    }

    /**
     * Create a new CostType event
     *
     * @param company
     * @param to
     * @param from
     * @param type
     * @param weightcost
     * @param volumecost
     * @param maxWeight
     * @param maxVolume
     * @param duration
     * @param frequency
     * @param day
     * @return CostType
     */
    public static CostType createCostType(String company, String to, String from, String type, int weightcost, int volumecost, int maxWeight, int maxVolume, int duration, int frequency, String day, long date) {
        CostType result = new CostType();
        result.setCompany(company);
        result.setTo(to);
        result.setFrom(from);
        result.setType(type);
        result.setWeightcost(BigInteger.valueOf(weightcost));
        result.setVolumecost(BigInteger.valueOf(volumecost));
        result.setMaxWeight(BigInteger.valueOf(maxWeight));
        result.setMaxVolume(BigInteger.valueOf(maxVolume));
        result.setDuration(BigInteger.valueOf(duration));
        result.setFrequency(BigInteger.valueOf(frequency));
        result.setDay(day);
        result.setDate(date);
        return result;
    }

    /**
     * Create a new DicontinueType event
     *
     * @param to
     * @param from
     * @param company
     * @param type
     * @param date
     * @return
     */
    public static DiscontinueType createDiscontinueType(String to, String from, String company, String type, long routedate, long date) {
        DiscontinueType result = new DiscontinueType();
        result.setTo(to);
        result.setFrom(from);
        result.setCompany(company);
        result.setType(type);
        result.setDate(date);
        result.setRoutedate(routedate);
        return result;
    }

    /**
     * Create a new MailType event
     *
     * @param day
     * @param to
     * @param from
     * @param weight
     * @param volume
     * @param priority
     * @param date
     * @return
     */
    public static MailType createMailType(String day, String to, String from, int weight, int volume, String priority, long date) {
        MailType result = new MailType();
        result.setDay(day);
        result.setTo(to);
        result.setFrom(from);
        result.setWeight(BigInteger.valueOf(weight));
        result.setVolume(BigInteger.valueOf(volume));
        result.setPriority(priority);
        result.setDate(date);
        return result;
    }

    /**
     * Create a new PriceType event
     *
     * @param to
     * @param from
     * @param priority
     * @param weightcost
     * @param volumecost
     * @param date
     * @return
     */
    public static PriceType createPriceType(String to, String from, String priority, int weightcost, int volumecost, long date) {
        PriceType result = new PriceType();
        result.setTo(to);
        result.setFrom(from);
        result.setPriority(priority);
        result.setWeightcost(BigInteger.valueOf(weightcost));
        result.setVolumecost(BigInteger.valueOf(volumecost));
        result.setDate(date);
        return result;

    }

    /**
     * Create a new TimelimitType event
     *
     * @param to
     * @param from
     * @param priority
     * @param maxDeliveryTime
     * @param date
     * @return
     */
    public static TimelimitType createTimelimitType(String to, String from, String priority, int maxDeliveryTime, long date) {
        TimelimitType result = new TimelimitType();
        result.setFrom(from);
        result.setTo(to);
        result.setMaxDeliveryTime(BigInteger.valueOf(maxDeliveryTime));
        result.setPriority(priority);
        result.setDate(date);
        return result;
    }

    /**
     * Get the CostType objects from a list of Events that are still valid
     *
     * @param events List<Event>
     * @return List of CostType objects that haven't been discontinued
     * @throws JAXBException
     */
    public static List<CostType> getActiveCostTypes(List<Event> events) {
        List<CostType> result = new LinkedList<CostType>();
        List<DiscontinueType> discontinueTypes = getDiscontinueTypes(events);
        for (Event e : events) {
            if (e instanceof CostType) {
                CostType c = (CostType) e;
                boolean discontinued = false;
                DiscontinueType toRemove = null;
                for (DiscontinueType d : discontinueTypes) {
                    if (!discontinued && d.getCompany().equals(c.getCompany()) && d.getFrom().equals(c.getFrom()) && d.getTo().equals(c.getTo()) && d.getType().equals(c.getType()) && d.getDate() > c.getDate() && d.getRoutedate().equals(c.getDate())) {
                        discontinued = true;
                        toRemove = d;
                    }
                }
                if (!discontinued) {
                    result.add(c);
                    discontinueTypes.remove(toRemove);
                }
            }
        }
        return result;
    }

    /**
     * Get all PriceTypes that haven't been updated
     *
     * @return
     * @throws JAXBException
     */
    public static Set<PriceType> getPriceTypes(List<Event> events) {
        Set<PriceType> result = new HashSet<PriceType>();
        for (Event e : events) {
            if (e instanceof PriceType) {
                PriceType newP = (PriceType) e;
                for (PriceType p : new HashSet<PriceType>(result)) {
                    if (p.getFrom().equals(newP.getFrom()) && p.getTo().equals(newP.getTo()) && p.getPriority().equals(newP.getPriority()) && newP.getDate() > p.getDate()) {
                        result.remove(p);
                    }
                }
                result.add(newP);
            }
        }
        return result;
    }

    /**
     * Get all discontinue types from list of events
     *
     * @return List of DiscontinueTypes
     * @throws JAXBException
     */
    public static List<DiscontinueType> getDiscontinueTypes(List<Event> events) {
        List<DiscontinueType> result = new LinkedList<DiscontinueType>();
        for (Event e : events) {
            if (e instanceof DiscontinueType) {
                result.add((DiscontinueType) e);
            }
        }
        return result;
    }

    /**
     * Get all mail types from list of events
     *
     * @return List<MailType>
     * @throws JAXBException
     */
    public static List<MailType> getMailTypes(List<Event> events) {
        List<MailType> result = new LinkedList<MailType>();
        for (Event e : events) {
            if (e instanceof MailType) {
                result.add((MailType) e);
            }
        }
        return result;
    }

    /**
     * Drop all events of a list of events that are after a certain event
     *
     * @param events
     * @param end
     * @return filtered events
     */
    public static List<Event> dropEventsAfter(List<Event> events, Event end) {
        List<Event> result = new LinkedList<Event>();
        for (Event e : events) {
            if (e.getDate() <= end.getDate()) {
                result.add(e);
            }
        }
        return result;
    }
}
